Vulnerable-Flask-App

Author: Khasan Abdurakhmanov Author's Avatar

Affiliation: Innopolis University Author's Avatar

TABLE OF CONTENTS

Introduction

This project is intentionally designed as a vulnerable Flask application for the purpose of education. Its primary goal is to showcase a variety of common web vulnerabilities that frequently pose a risk in the digital world. By providing a real-life simulation of these vulnerabilities, this project serves as an invaluable learning tool for a diverse range of individuals. It presents a practical and interactive environment that enables security enthusiasts, software developers, and students to gain a deeper understanding of web vulnerabilities. This hands-on experience not only helps them comprehend the nature of these vulnerabilities, but it also equips them with the knowledge and skills to identify and mitigate such risks in the future.

Tools and Setup

In order to gain a comprehensive understanding of the vulnerabilities you are dealing with and to exploit them effectively, it is imperative to have a collection of specific tools at your disposal. Here is a list of the essential tools you will need:

  1. Postman

    Postman has garnered popularity as a highly effective tool for testing APIs. It provides an intuitive user interface that allows you to easily send HTTP requests and closely examine the responses you receive. You have the option to use either the desktop application or the web version, depending on your preference.


  1. Docker

    Docker is a platform that has gained wide acceptance for containerization. It greatly simplifies the process of managing and deploying applications in containers, making it a vital tool for any developer.


  1. Burp Suite

    Burp Suite is a formidable tool in the realm of web application security testing. It offers a wide array of features, suitable for both manual and automated testing. It plays a vital role in intercepting and analyzing HTTP requests and responses, making it indispensable for security testing.


Postman and Burp Suite are both popular tools used for API testing, but they serve different purposes:

Combining Postman and Burp Suite in this guide can be beneficial for the following reasons:

  1. Postman can be used to create and execute API requests, while Burp Suite can intercept and analyze the traffic to identify potential security issues.

  2. Burp Suite can be used to test APIs for vulnerabilities like injection flaws, broken authentication, and security misconfigurations, which are covered in the OWASP API Security Top 10.

  3. Using both tools together provides a more comprehensive approach to API testing, leveraging the strengths of each tool.

Docker is chosen as the containerization tool in this guide for several reasons:

  1. Docker is the most popular and widely-used containerization platform, with a large and active community.

  2. Docker provides a consistent and reproducible environment for running applications, making it easier to set up and deploy application.

  3. Docker simplifies the management of dependencies and ensures that the application runs consistently across different environments.


Setting Up Flask

There are two ways to set up Flask: using Docker Engine or manually. Both methods are detailed below.

Method 1: Using Docker Engine

  1. Clone the Vulnerable-Flask-App Repository
    First, clone the Vulnerable-Flask-App repository from GitHub to your local machine:
git clone https://github.com/SNE-M23-SN/Vulnerable-Flask-App.git cd Vulnerable-Flask-App

  1. Build and Run the Flask Container
    Use Docker to build and run the Vulnerable-Flask-App container:
docker build -t flask .

docker run -itd --name Flask-App -p 5050:5050 flask

  1. Access Flask
    Open http://localhost:5050 in your web browser to access the Vulnerable-Flask-App interface.


Method 2: Setting Up Flask Manually

Prerequisites:

There are a few prerequisites for setting up the Vulnerable Flask App:

  1. Python 2.7: The app requires Python 2.7 to be installed on the system. Python 2.7 reached its end of life on January 1, 2020, so it’s important to use this specific version for compatibility with the app.

  2. Virtual Environment: It’s recommended to create a Python 2.7 virtual environment to isolate the project’s dependencies. You can use tools like virtualenv or venv (if available) to create the virtual environment.

  3. Required Packages: The app requires several Python packages to be installed. These packages are listed in the requirements.txt file located in the app directory. You can install them using pip install -r app/requirements.txt.

  4. MySQL Headers: When installing the MySQL-python package, you may encounter an error related to the missing my_config.h header file. To resolve this, you need to download the header file and place it in the correct location:

sudo wget https://raw.githubusercontent.com/paulfitz/mysql-connector-c/master/include/my_config.h -O /usr/include/mysql/my_config.h

After ensuring these prerequisites are met, you should be able to set up and run the Vulnerable Flask App without any issues.


  1. Create a Virtual Environment:

    python2.7 -m pip install virtualenv
    python2.7 -m virtualenv venv
    source venv/bin/activate
    

    This creates a new virtual environment using Python 2.7 and activates it.

  2. Install Dependencies:

    pip install -r app/requirements.txt
    

    This installs the required packages for the Vulnerable Flask App.

  3. Run the Flask App:

    cd app/
    export FLASK_APP=app.py
    flask run
    

    This changes the directory to the `app` folder, sets the `FLASK_APP` environment variable, and runs the Flask app.

Now, the Vulnerable Flask App should be running on http://localhost:5000. You can access the app in your web browser.


Exploiting Vulnerabilities

Let’s begin our exploitation journey by examining the first vulnerability in the Vulnerable Flask App:

1. Register a new user

Certainly, let’s dive into the details of the /register/user endpoint:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/register/user', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /register/user URL path.
  2. Request Handling:

    • The reg_customer() function is the view function that handles the requests to this endpoint.
    • It first tries to retrieve the JSON data from the request using request.json.
    • If the content variable (which holds the JSON data) is not empty, it proceeds to process the request.
  3. Username and Password Extraction:

    • The username and password values are extracted from the content dictionary.
  4. Password Hashing:

    • The password is hashed using the MD5 algorithm by calling hashlib.md5(password).hexdigest().
    • MD5 is an outdated and insecure hashing algorithm that should not be used for password storage. It is vulnerable to brute-force and rainbow table attacks.
  5. User Creation:

    • A new User object is created using the username and hashed password.
    • The new user is added to the database session using db.session.add(new_user).
    • The changes are committed to the database using db.session.commit().
  6. Response:

    • If the user creation is successful, a success message is constructed using the username and returned as a JSON response with a 200 status code.

    • If an exception occurs during the process, a JSON response with the error message and a 404 status code is returned.

Certainly, let’s dive into the details of the /register/user endpoint:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/register/user', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /register/user URL path.
  2. Request Handling:

    • The reg_customer() function is the view function that handles the requests to this endpoint.
    • It first tries to retrieve the JSON data from the request using request.json.
    • If the content variable (which holds the JSON data) is not empty, it proceeds to process the request.
  3. Username and Password Extraction:

    • The username and password values are extracted from the content dictionary.
  4. Password Hashing:

    • The password is hashed using the MD5 algorithm by calling hashlib.md5(password).hexdigest().
    • MD5 is an outdated and insecure hashing algorithm that should not be used for password storage. It is vulnerable to brute-force and rainbow table attacks.
  5. User Creation:

    • A new User object is created using the username and hashed password.
    • The new user is added to the database session using db.session.add(new_user).
    • The changes are committed to the database using db.session.commit().
  6. Response:

    • If the user creation is successful, a success message is constructed using the username and returned as a JSON response with a 200 status code.
    • If an exception occurs during the process, a JSON response with the error message and a 404 status code is returned.

Vulnerabilities and Potential Attacks:

  1. Weak Password Hashing: As mentioned earlier, the use of the MD5 hashing algorithm is a major security vulnerability. MD5 is considered insecure for password storage, and it should be replaced with a more secure algorithm like Bcrypt or Argon2.

  2. Lack of Password Salting: The code does not mention adding a unique salt to each password before hashing. Using a salt helps protect against rainbow table attacks and makes it harder for attackers to crack passwords in bulk.

  3. Potential for Username Enumeration: If the error message returned by the /register/user endpoint reveals whether a username already exists, it can lead to username enumeration. An attacker can use this information to determine valid usernames and focus their efforts on those accounts.

  4. Lack of Password Complexity Requirements: The code does not enforce any password complexity requirements, such as minimum length, use of special characters, or restrictions on common passwords. Weak passwords are easier to guess or crack.


2. Register a new customer

The /register/customer endpoint is used to register a new customer in the application. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/register/customer', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /register/customer URL path.
  2. Request Handling:

    • The reg_user() function is the view function that handles the requests to this endpoint.
    • It first tries to retrieve the JSON data from the request using request.json.
    • If the content variable (which holds the JSON data) is not empty, it proceeds to process the request.
  3. Data Extraction:

    • The username, password, first_name, last_name, and email values are extracted from the content dictionary.
    • The ccn (credit card number) is also extracted using content.get('ccn'). The get() method is used because ccn is nullable, meaning it can be None.
  4. Customer Creation:

    • A new Customer object is created using the extracted values: first_name, last_name, email, username, password, and ccn.
    • The new customer is added to the database session using db.session.add(new_customer).
    • The changes are committed to the database using db.session.commit().
  5. Response:

    • If the customer creation is successful, a success message is constructed using the username and returned as a JSON response with a 200 status code.

    • If an exception occurs during the process, a JSON response with the error message and a 404 status code is returned.

Vulnerabilities and Potential Attacks:

  1. Weak Password Hashing: The code does not mention how the passwords are hashed. Storing passwords in plaintext or using a weak hashing algorithm like MD5 is a major security risk.

  2. Storing Sensitive Data (Credit Card Numbers) in Plaintext: The ccn (credit card number) field is stored in the Customer model without any encryption or hashing. Storing sensitive data like credit card numbers in plaintext is a serious security vulnerability.

  3. Potential for SQL Injection: The code uses string formatting to construct SQL queries, which can lead to SQL injection vulnerabilities. An attacker could potentially inject malicious SQL code into the username, password, first_name, last_name, or email fields. But for this case, given that the /register/customer endpoint uses SQLAlchemy ORM to interact with the database, it is not directly vulnerable to SQL injection. SQLAlchemy ORM typically uses parameterized queries to interact with the database, which helps in preventing SQL injection attacks.

  4. Lack of Input Validation and Sanitization: The code does not perform any input validation or sanitization on the user-supplied data, such as checking for malicious characters or limiting the length of the fields. This can lead to other types of vulnerabilities, such as cross-site scripting (XSS) or directory traversal attacks.


3. Login

The /login endpoint is responsible for authenticating users and generating a JWT (JSON Web Token) for authorized access. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/login', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /login URL path.
  2. Request Handling:

    • The login() function is the view function that handles the requests to this endpoint.
    • It first retrieves the JSON data from the request using request.json.
  3. Username and Password Extraction:

    • The username and password values are extracted from the content dictionary.
  4. User Authentication:

    • The auth_user variable is assigned the result of querying the User model using User.query.filter_by(username=username, password=password).first().
    • This query checks if a user with the provided username and password exists in the database.
    • The /login endpoint will use the User table to authenticate the user and provide a JWT token if the credentials are valid.
  5. JWT Token Generation:

    • If the auth_user is not None, indicating a valid user, a JWT token is generated using jwt.encode().
    • The token payload includes the username, an expiration date (exp) calculated using the get_exp_date() function, the current time (iat), and an issuer (iss).
    • The token is signed using the app.config['SECRET_KEY_HMAC'] secret key.

  1. Response:
    • A Response object is created with a JSON payload indicating successful authentication and the username.
    • The JWT token is added to the Authorization header of the response using resp.headers['Authorization'].
    • The response status code is set to 200 (OK), and the mimetype is set to 'application/json'.
    • The response is returned.

  1. Error Handling:
    • If the auth_user is None, indicating an invalid username or password, a JSON response with an error message and a 404 status code is returned.
    • If an exception occurs during the process, a JSON response with an error message and a 404 status code is returned.

Vulnerabilities and Potential Attacks:

  1. Potential for Brute-Force or Credential Stuffing Attacks: The code does not implement any rate limiting or account lockout mechanisms. This makes the login endpoint susceptible to brute-force attacks or credential stuffing attacks using lists of common username and password combinations.

  2. Insecure JWT Token Handling: The code uses a fixed secret key (app.config['SECRET_KEY_HMAC']) to sign the JWT tokens. If this secret key is compromised, an attacker can forge valid tokens and gain unauthorized access.


4. Fetch a customer

The /fetch/customer endpoint is used to retrieve customer details based on a provided customer ID. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/fetch/customer', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /fetch/customer URL path.
  2. Token Validation:

    • The token variable is extracted from the Authorization header of the request using request.headers.get('Authorization').

    • If the token is missing or does not start with Bearer , a JSON response with an error message and a 403 status code is returned.

    • The token is extracted by removing the Bearer prefix using token.split(' ')[1].

    • The verify_jwt() function is called to verify the validity of the JWT token. If the token is invalid or expired, a JSON response with an error message and a 403 status code is returned.

  3. Customer Details Fetching:

    • After successful token validation, the request body is retrieved using request.json.

    • If the content variable (which holds the request body) is not empty, the customer_id is extracted from it.

    • The Customer model is queried using Customer.query.get(customer_id) to fetch the customer record with the specified customer_id.

    • If a matching customer record is found, a dictionary customer_dict is created with the customer details, including id, firstname, lastname, email, cc_num, and username.

    • The customer_dict is returned as a JSON response with a 200 status code.

  4. Error Handling:

    • If no customer record is found, a JSON response with an error message and a 404 status code is returned.

    • If the request body is empty or invalid, a JSON response with an error message and a 400 status code is returned.

The /fetch/customer endpoint requires a valid JWT token in the Authorization header for authentication. It then fetches the customer details based on the provided customer ID and returns the information in a JSON response.


The /fetch/customer endpoint is vulnerable to sensitive information exposure because it returns the customer’s credit card number (CCN) in the API response:

customer_dict = {
    'id': customer_record.id,
    'firstname': customer_record.first_name,
    'lastname': customer_record.last_name,
    'email': customer_record.email,
    'cc_num': customer_record.ccn,
    'username': customer_record.username
}
return jsonify(customer_dict), 200

By including the cc_num: customer_record.ccn in the customer_dict, the endpoint exposes the customer’s credit card number in the JSON response.

This is a security vulnerability because it can lead to data breaches and misuse of sensitive information if the API response is intercepted or accessed by unauthorized parties.


5. Get specific customer

The /get/<cust_id> endpoint is used to fetch the details of a customer based on the provided customer ID. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/get/<cust_id>', methods=['GET']).
    • This means the endpoint accepts HTTP GET requests at the /get/<cust_id> URL path, where <cust_id> is a dynamic parameter representing the customer ID.
  2. Token Validation:

    • The token variable is extracted from the Authorization header of the request using request.headers.get('Authorization').
    • If the token is missing or does not start with "Bearer ", a JSON response with an error message and a 403 status code is returned.
    • The token is extracted by removing the Bearer prefix using token.split(' ').
    • The insecure_verify() function is called to verify the validity of the JWT token. If the token is invalid, a JSON response with an error message and a 403 status code is returned.
  3. Customer Details Fetching:

    • If the cust_id parameter is provided, the Customer model is queried using Customer.query.get(cust_id) to fetch the customer record with the specified cust_id.
    • If a matching customer record is found, a dictionary customer_dict is created with the customer details, including id, firstname, lastname, email, cc_num, and username.
    • The customer_dict is returned as a JSON response with a 200 status code.

  1. Error Handling:
    • If no customer record is found, a JSON response with an error message and a 404 status code is returned.
    • If the cust_id parameter is invalid, a JSON response with an error message and a 400 status code is returned.
    • If an exception occurs during the process, a JSON response with an error message and a 500 status code is returned.

The /get/<cust_id> endpoint requires a valid JWT token in the Authorization header for authentication. It then fetches the customer details based on the provided customer ID and returns the information in a JSON response.

It’s important to note that the code uses the insecure_verify() function to verify the JWT token, which is a vulnerable implementation. The insecure_verify() function sets verify=False when decoding the token, allowing any token to be accepted as valid. This is a major security vulnerability that should be addressed.

That’s mean that we can exploit it look at JWT token of our admin:

Now we will try to mistype the user field in JWT token:

This is a major security vulnerability that allowing any token to be accepted as valid.


The /search endpoint is vulnerable to SQL injection due to the direct insertion of user input into the SQL query. Here’s a detailed explanation:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route('/search', methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /search URL path.
  2. Token Validation:

    • The token variable is extracted from the Authorization header of the request using request.headers.get('Authorization').
    • If the token is missing or does not start with "Bearer ", a JSON response with an error message and a 403 status code is returned.
    • The token is extracted by removing the Bearer prefix using token.split(' ').
    • The verify_jwt() function is called to verify the validity of the JWT token. If the token is invalid, a JSON response with an error message and a 403 status code is returned.
  3. Search Functionality:

    • The request body is retrieved using request.json.

    • If the content variable (which holds the request body) is empty or does not contain a search key, a JSON response with an error message and a 400 status code is returned.

    • The search_term is extracted from the content dictionary.

    • The SQL query is constructed using string formatting: str_query = "SELECT first_name, last_name, username FROM customer WHERE username = '{}'".format(search_term).

    • The SQL query is executed using db.engine.execute(str_query), and the results are returned as a JSON response with a 200 status code.

Vulnerability and Potential Exploit:
The main vulnerability in this endpoint is the use of direct string formatting to construct the SQL query. This approach is susceptible to SQL injection attacks.

An attacker can craft a malicious search_term value that includes SQL injection payloads, such as ' OR '1'='1. When this value is inserted into the SQL query, it will result in the following query:

SELECT first_name, last_name, username FROM customer WHERE username = '' OR '1'='1'

This query will return all customer records, as the '1'='1' condition is always true. The attacker can then extract sensitive information from the database or even execute arbitrary SQL commands.


7. XXE

The /xxe_uploader endpoint is vulnerable to XML External Entity (XXE) injection attacks. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route("/xxe_uploader", methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /xxe_uploader URL path.
  2. File Upload Handling:

    • The endpoint checks if the request method is POST.
    • It then retrieves the uploaded file from the request using request.files['file'].
    • If no file is selected, the endpoint returns a 400 Bad Request response.
  3. File Handling:

    • The endpoint generates a random filename using random.randint(1, 100) and secure_filename(f.filename) to avoid filename clashes.
    • The file is saved locally using f.save(file_path), where file_path is constructed using tempfile.mkdtemp() to create a temporary directory.
  4. Document Processing:

    • The endpoint uses the Document class from the docx library to parse the uploaded file.
    • It then extracts the text content from the document paragraphs and joins them into a single string using '\n'.join([para.text for para in document.paragraphs]).
  5. Cleanup and Response:

    • The temporary directory containing the uploaded file is removed using shutil.rmtree(os.path.dirname(file_path)).
    • The extracted text content is then rendered in the view.html template and returned as the response.

Vulnerability and Potential Exploit:

The main vulnerability in this endpoint is the lack of proper configuration for the XML parser used by the Document class. By default, the lxml library, which is used by the docx library, is vulnerable to XML External Entity (XXE) attacks.

An attacker can craft a malicious DOCX file containing XML entities that reference local files or internal servers. When the file is parsed by the Document class, the contents of the referenced files will be returned in the response.

The xxe file can be find in app/Files/xxe.docx

When this file is uploaded and processed by the /xxe_uploader endpoint, the contents of the /etc/passwd file on the server will be included in the response.


8. YAML

The /yaml_hammer endpoint is vulnerable to YAML deserialization attacks. Here’s a detailed explanation of how it works:

  1. Endpoint Definition:

    • The endpoint is defined using the Flask route decorator @app.route("/yaml_hammer", methods=['POST']).
    • This means the endpoint accepts HTTP POST requests at the /yaml_hammer URL path.
  2. File Upload Handling:

    • The endpoint checks if the request method is POST.
    • It then retrieves the uploaded file from the request using request.files['file'].
  3. File Handling:

    • The endpoint generates a random filename using random.randint(1, 100) and secure_filename(f.filename) to avoid filename clashes.
    • The file is saved locally using f.save(file_path), where file_path is constructed using the current working directory (os.getcwd()) and the generated filename.
  4. YAML Deserialization:

    • The endpoint reads the contents of the uploaded file using yfile.read() and assigns it to the y variable.
    • The yaml.load(y) function is then called to deserialize the YAML data, and the resulting object is stored in the ydata variable.
  5. Response:

    • The ydata object is converted to a JSON string using json.dumps(ydata) and passed to the view.html template, which is then rendered and returned as the response.

Vulnerability and Potential Exploit:

The main vulnerability in this endpoint is the use of the yaml.load() function to deserialize the YAML data. The yaml.load() function can execute arbitrary code if given malicious YAML input. An attacker can craft a YAML payload that, when deserialized, executes commands on the server.

For example, an attacker could create a YAML file with the following content:

!!python/object/apply:os.system
- 'ls -la'

When this YAML file is uploaded and processed by the /yaml_hammer endpoint, the os.system('ls -la') command will be executed on the server, and the output will be returned in the response.


Conclusion

The Vulnerable Flask App is a deliberately insecure web application designed for educational purposes. It showcases various security vulnerabilities that can be found in real-world web applications. By understanding and exploiting these vulnerabilities, security professionals and developers can learn how to identify and mitigate similar issues in their own projects.

The key vulnerabilities demonstrated in the app include:

  1. SQL Injection: The /search endpoint is vulnerable to SQL injection due to direct insertion of user input into SQL queries. An attacker can craft malicious SQL payloads to retrieve or manipulate data from the database.

  2. XML External Entity (XXE) Injection: The /xxe_uploader endpoint processes DOCX files without properly configuring the XML parser. An attacker can exploit this vulnerability to read local files or perform SSRF attacks.

  3. Insecure JWT Handling: The /get/<cust_id> endpoint uses an insecure way of verifying JWT tokens by setting verify=False. This allows any token to be accepted as valid, enabling an attacker to forge tokens and gain unauthorized access.

  4. Weak Password Storage: The /register/user and /login endpoints store passwords using the insecure MD5 hashing algorithm and sometimes in plaintext. This makes it easy for attackers to crack passwords and gain access to user accounts.

  5. Sensitive Information Exposure: The /fetch/customer and /get/<cust_id> endpoints return sensitive information such as credit card numbers in the API responses. If intercepted, this data can be misused by attackers.

  6. YAML Deserialization: The /yaml_hammer endpoint uses unsafe YAML deserialization, allowing an attacker to execute arbitrary code by crafting malicious YAML payloads.

By understanding and addressing these vulnerabilities, developers can improve the security of their own web applications and protect against similar attacks. The Vulnerable Flask App serves as a valuable learning resource for security professionals, developers, and students to enhance their skills in identifying and mitigating web application vulnerabilities.

Expand allBack to topGo to bottom